/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;

using Anculus.Core;

namespace Galaxium.Protocol.Msn
{
	public abstract class AbstractMsnP2PApplication : IMsnP2PApplication
	{
		public event EventHandler Complete;
		public event EventHandler Error;
		
		IMsnP2PBridge _bridge;
		MsnAccount _local;
		MsnContact _remote;
		MsnSession _session;
		Queue<P2PMessage> _outQueue = new Queue<P2PMessage> ();
		
		static uint _dbgAppCount = 0;
		protected uint _dbgAppID = _dbgAppCount++;
		
		public virtual uint AppID
		{
			// Return the AppID MsnP2PUtility got from the MsnP2PApplicationAttribute
			get { return MsnP2PUtility.GetAppID (this); }
		}
		
		public IMsnP2PBridge Bridge
		{
			get { return _bridge; }
		}
		
		public MsnAccount Local
		{
			get { return _local; }
		}
		
		public MsnContact Remote
		{
			get { return _remote; }
		}
		
		public MsnSession Session
		{
			get { return _session; }
		}
		
		protected Queue<P2PMessage> OutQueue
		{
			get { return _outQueue; }
		}
		
		public AbstractMsnP2PApplication (MsnAccount local, MsnContact remote)
		{
			_local = local;
			_remote = remote;
			_session = _local.Session;
			
			Log.Debug ("P2PApp {0} created ({1})", _dbgAppID, GetType().Name);
			
			MigrateToOptimalBridge ();
		}
		
		public virtual void Dispose ()
		{
			Log.Debug ("P2PApp {0} disposed", _dbgAppID);
			
			if (_bridge != null)
			{
				_bridge.BridgeReady -= OnBridgeReady;
				_bridge.BridgeClosed -= OnBridgeClosed;
			}
		}
		
		protected virtual void MigrateToOptimalBridge ()
		{
			if (_remote.DirectBridge != null)
			{
				Migrate (_remote.DirectBridge);
				return;
			}
			
			//Log.Debug ("Calling GETSWITCHBOARD from the AbstractMsnP2PApplication");
			
			SBConnection sb = _session.GetSwitchboard (false, _remote);
			Migrate (sb);
		}
		
		protected virtual void Migrate (IMsnP2PBridge bridge)
		{
			if (bridge == _bridge)
				return;
			
			Log.Debug ("P2PApp {0} migrating to {1}", _dbgAppID, bridge);
			
			if (_bridge != null)
			{
				_bridge.BridgeReady -= OnBridgeReady;
				_bridge.BridgeClosed -= OnBridgeClosed;
			}
			
			_bridge = bridge;
			_bridge.BridgeReady += OnBridgeReady;
			_bridge.BridgeClosed += OnBridgeClosed;
			
			ProcessOutQueue (false);
		}
		
		protected virtual void OnBridgeReady (object sender, EventArgs args)
		{
			//Log.Debug ("P2PApp {0} bridge ready, {1} messages queued", _dbgAppID, _outQueue.Count);
			ProcessOutQueue (true);
		}
		
		protected virtual void OnBridgeClosed (object sender, EventArgs args)
		{
			Log.Debug ("P2PApp {0} bridge closed", _dbgAppID);
			
			_bridge.BridgeReady -= OnBridgeReady;
			_bridge.BridgeClosed -= OnBridgeClosed;
			
			_bridge = null;
			
			if (_outQueue.Count > 0)
				MigrateToOptimalBridge ();
		}
		
		public abstract bool ProcessMessage (IMsnP2PBridge bridge, P2PMessage msg);
		
		public virtual void Send (P2PMessage msg, AckHandler ackHandler)
		{
			// If not an ack, set the footer to the application ID
			if ((msg.Header.Flags & P2PHeaderFlag.Ack) != P2PHeaderFlag.Ack)
				msg.Footer = MsnP2PUtility.GetAppID (this);
			
			P2PMessage[] msgs = MsnP2PUtility.SplitMessage (msg, _bridge != null ? _bridge.MaxDataSize : 1150);
			
			lock (_outQueue)
			{
				foreach (P2PMessage m in msgs)
					_outQueue.Enqueue (m);
			}
			
			if (ackHandler != null)
				MsnP2PUtility.RegisterAckHandler (msg.Header.AckID, ackHandler);
			
			ProcessOutQueue (false);
		}
		
		public void Send (P2PMessage msg)
		{
			Send (msg, null);
		}
		
		protected virtual void ProcessOutQueue (bool forceReady)
		{
			if (_outQueue.Count == 0)
			{
				//Log.Debug ("Out queue is empty");
				return;
			}
			
			if (_bridge == null)
			{
				//Log.Debug ("ProcessOutQueue with no bridge");
				MigrateToOptimalBridge ();
				return;
			}
			
			if (!(_bridge.Ready || forceReady))
			{
				//Log.Debug ("Bridge not ready");
				return;
			}
			
			//Log.Debug ("P2PApp {0} sending", _dbgAppID);
			
			lock (_outQueue)
				_bridge.Send (_outQueue.Dequeue ());
		}
		
		protected void OnComplete ()
		{
			Log.Debug ("P2PApp {0} complete", _dbgAppID);
			
			if (this.Complete != null)
				this.Complete (this, EventArgs.Empty);
		}
		
		protected void OnError ()
		{
			if (this.Error != null)
				this.Error (this, EventArgs.Empty);
		}
	}
	
	public abstract class AbstractMsnP2PSessionApplication : AbstractMsnP2PApplication, IMsnP2PSessionApplication
	{
		public event EventHandler Began;
		public event EventHandler LocallyCancelled;
		public event EventHandler RemotelyCancelled;
		
		MsnP2PSession _p2pSession;
		
		public virtual bool AutoAccept
		{
			get { return false; }
		}
		
		public MsnP2PSession P2PSession
		{
			get { return _p2pSession; }
			set
			{
				_p2pSession = value;
				
				_p2pSession.Closing += P2PSessionClosing;
			}
		}
		
		public AbstractMsnP2PSessionApplication (MsnP2PSession p2pSession)
			: base (p2pSession.Local, p2pSession.Remote)
		{
			P2PSession = p2pSession;
			
			Log.Debug ("P2PApp {0} associated with session {1}", _dbgAppID, _p2pSession.SessionID);
		}
		
		public AbstractMsnP2PSessionApplication (MsnAccount local, MsnContact remote)
			: base (local, remote)
		{
		}
		
		public override void Dispose ()
		{
			_p2pSession.Closing -= P2PSessionClosing;
			
			base.Dispose ();
		}
		
		public virtual bool CheckInvite (SLPRequestMessage invite)
		{
			// If the invite isn't for us, it isn't valid
			return invite.To == invite.Session.Account;
		}
		
		public abstract string CreateInviteContext ();
		
		public virtual void Begin ()
		{
			OnBegan ();
		}
		
		public virtual void Accept ()
		{
			_p2pSession.Accept ();
		}
		
		public virtual void Decline ()
		{
			_p2pSession.Decline ();
		}
		
		public virtual void Cancel ()
		{
			OnLocallyCancelled ();
			_p2pSession.Close ();
		}
		
		public override void Send (P2PMessage msg, AckHandler ackHandler)
		{
			msg.Header.SessionID = _p2pSession.SessionID;
			
			// Set the MessageID, but only if it's not already set
			if (msg.Header.MessageID == 0)
				msg.Header.MessageID = _p2pSession.NextID ();
			
			base.Send (msg, ackHandler);
		}
		
		internal void OnBegan ()
		{
			Log.Debug ("P2PApp {0} began", _dbgAppID);
			
			if (this.Began != null)
				this.Began (this, EventArgs.Empty);
		}
		
		internal void OnLocallyCancelled ()
		{
			Log.Debug ("P2PApp {0} cancelled locally", _dbgAppID);
			
			if (this.LocallyCancelled != null)
				this.LocallyCancelled (this, EventArgs.Empty);
		}
		
		internal void OnRemotelyCancelled ()
		{
			Log.Debug ("P2PApp {0} cancelled remotely", _dbgAppID);
			
			if (this.RemotelyCancelled != null)
				this.RemotelyCancelled (this, EventArgs.Empty);
		}
		
		void P2PSessionClosing (object sender, EventArgs args)
		{
			OnRemotelyCancelled ();
		}
	}
}
